/*
 * @(#) $Header$
 *
 * Copyright (C) 2009  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

import java.io.IOException;

import static ca.forklabs.baselib.io.Streams.asBufferedReader;
import static ca.forklabs.baselib.io.Streams.asPrintWriter;

/**
 * Class {@code FileUtil} provides utility classes to make it easier to interact
 * with files by offering factory methods for streams and reader/writers as well
 * as one-liners to read and write.
 * <p>
 * <b>NOTE</b>: appending using charset {@link #UTF_16} will put a
 * <a href="http://en.wikipedia.org/wiki/UTF-16/UCS-2#Byte_order_encoding_schemes">Zero-width non-breaking space</a>
 * for each append call.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.io.FileUtil">Daniel Léonard</a>
 * @version $Revision$
 */
public class FileUtil {

//---------------------------
// Class variables
//---------------------------

   /** The charset {@code US-ACII}. */
   public static final Charset US_ASCII = Charset.forName("US-ASCII"); //$NON-NLS-1$
   /** The charset {@code ISO-8859-1}. */
   public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); //$NON-NLS-1$
   /** The charset {@code UTF-8}. */
   public static final Charset UTF_8 = Charset.forName("UTF-8");   //$NON-NLS-1$
   /** The charset {@code UTF-16BE}. */
   public static final Charset UTF_16_BE = Charset.forName("UTF-16BE"); //$NON-NLS-1$
   /** The charset {@code UTF-16LE}. */
   public static final Charset UTF_16_LE = Charset.forName("UTF-16LE"); //$NON-NLS-1$
   /** The charset {@code UTF-16}. */
   public static final Charset UTF_16 = Charset.forName("UTF-16"); //$NON-NLS-1$


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    */
   protected FileUtil() {
   // allow sub-classes only
      }


//---------------------------
// Class methods
//---------------------------

   /**
    * Gets the system end-of-line token as a {@link String}.
    * @return   the end-of-line token.
    */
   @SuppressWarnings("nls")
   protected static String eol() {
      String eol = System.getProperty("line.separator");
      return eol;
      }


//---------------------------
// Manipulation operations
//---------------------------

// Copy    Overloaded. Copies an existing file to a new file.
// Delete  Deletes the specified file. An exception is not thrown if the specified file does not exist.
// Move    Moves a specified file to a new location, providing the option to specify a new file name.
// Open    Overloaded. Opens a FileStream on the specified path.
// Exists  Determines whether the specified file exists.
// Replace Overloaded. Replaces the contents of a specified file with the contents of another file, deleting the original file, and creating a backup of the replaced file.

   /**
    * Checks if the given filename refers to a file that exists.
    * @param   filename   the filename.
    * @return   {@code true} if the filename refers to a file that exists,
    *           {@code false} otherwise.
    */
   public static boolean exist(String filename) {
      File file = new File(filename);
      boolean exists = file.exists();
      return exists;
      }

   /**
    * Deletes the specified file. If the file does not exist or if the file is
    * a directory, nothing is done.
    * @param   filename   the file to delete.
    * @return   {@code true} if the file has been deleted,
    *           {@code false} otherwise.
    */
   public static boolean delete(String filename) {
      File file = new File(filename);
      boolean deleted = delete(file);
      return deleted;
      }

   /**
    * Deletes the specified file. If the file does not exist or if the file is
    * a directory, nothing is done.
    * @param   file   the file to delete.
    * @return   {@code true} if the file has been deleted,
    *           {@code false} otherwise.
    */
   public static boolean delete(File file) {
      boolean deleted = (file.isDirectory()) ? false : file.delete();
      return deleted;
      }


//---------------------------
// openRead()
//---------------------------

   /**
    * Opens a binary file for reading.
    * @param   filename   the file to open.
    * @return   an input stream from the file.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static InputStream openRead(String filename) throws IOException {
      File file = new File(filename);
      InputStream is = openRead(file);
      return is;
      }

   /**
    * Opens a binary file for reading.
    * @param   file   the file to open.
    * @return   an input stream from the file.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static InputStream openRead(File file) throws IOException {
      InputStream is = new FileInputStream(file);
      return is;
      }

   /**
    * Opens the url for binary reading.
    * @param   url   the url.
    * @return   an input stream from the url.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static InputStream openRead(URL url) throws IOException {
      InputStream is = url.openStream();
      return is;
      }

   /**
    * Opens the java resource for binary reading.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @return   an input stream from the resource.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static InputStream openRead(Class<?> clazz, String resource) throws IOException {
      InputStream is = clazz.getResourceAsStream(resource);
      return is;
      }


   /**
    * Opens a text file for reading using the default charset.
    * @param   filename   the file to open.
    * @return   a buffered reader from the file.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(String filename) throws IOException {
      Charset charset = Charset.defaultCharset();
      BufferedReader br = openReadText(filename, charset);
      return br;
      }

   /**
    * Opens a text file for reading using the default charset.
    * @param   file   the file to open.
    * @return   a buffered reader from the file.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(File file) throws IOException {
      Charset charset = Charset.defaultCharset();
      BufferedReader br = openReadText(file, charset);
      return br;
      }

   /**
    * Opens the url for text reading using the default charset.
    * @param   url   the url.
    * @return   a reader from the url.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(URL url) throws IOException {
      Charset charset = Charset.defaultCharset();
      BufferedReader br = openReadText(url, charset);
      return br;
      }

   /**
    * Opens the java resource for text reading using the default charset.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @return   a reader from the resource.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(Class<?> clazz, String resource) throws IOException {
      Charset charset = Charset.defaultCharset();
      BufferedReader br = openReadText(clazz, resource, charset);
      return br;
      }

   /**
    * Converts the input stream into a reader using the default charset.
    * @param   is   the input stream
    * @return   a reader from the stream.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(InputStream is) throws IOException {
      Charset charset = Charset.defaultCharset();
      BufferedReader br = openReadText(is, charset);
      return br;
      }

   /**
    * Opens a text file for reading.
    * @param   filename   the file to open.
    * @param   charset   the encoding of the text.
    * @return   a buffered reader from the file.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(String filename, Charset charset) throws IOException {
      File file = new File(filename);
      BufferedReader br = openReadText(file, charset);
      return br;
      }

   /**
    * Opens a text file for reading.
    * @param   file   the file to open.
    * @param   charset   the encoding of the text.
    * @return   a buffered reader from the file.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static BufferedReader openReadText(File file, Charset charset) throws IOException {
      InputStream is = openRead(file);
      BufferedReader br = asBufferedReader(is, charset);
      return br;
      }

   /**
    * Opens the url for text reading.
    * @param   url   the url.
    * @param   charset   the encoding of the text.
    * @return   a reader from the url.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static BufferedReader openReadText(URL url, Charset charset) throws IOException {
      InputStream is = openRead(url);
      BufferedReader br = asBufferedReader(is, charset);
      return br;
      }

   /**
    * Opens the java resource for text reading.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @param   charset   the encoding of the text.
    * @return   a reader from the resource.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static BufferedReader openReadText(Class<?> clazz, String resource, Charset charset) throws IOException {
      InputStream is = openRead(clazz, resource);
      BufferedReader br = asBufferedReader(is, charset);
      return br;
      }

   /**
    * Converts the input stream into a reader.
    * @param   is   the input stream
    * @param   charset   the encoding of the text.
    * @return   a reader from the stream.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static BufferedReader openReadText(InputStream is, Charset charset) throws IOException {
      BufferedReader br = asBufferedReader(is, charset);
      return br;
      }


//---------------------------
// openWrite()
//---------------------------

   /**
    * Opens a file for writing.
    * @param   filename   the file to open.
    * @return   a stream to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static OutputStream openWrite(String filename) throws IOException {
      File file = new File(filename);
      OutputStream os = openWrite(file);
      return os;
      }

   /**
    * Opens a file for writing.
    * @param   file   the file to open.
    * @return   a stream to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static OutputStream openWrite(File file) throws IOException {
      OutputStream os = new FileOutputStream(file);
      return os;
      }

   /**
    * Opens a text file for writing.
    * @param   filename   the file to open.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static PrintWriter openWriteText(String filename) throws IOException {
      Charset charset = Charset.defaultCharset();
      PrintWriter pw = openWriteText(filename, charset);
      return pw;
      }

   /**
    * Opens a text file for writing.
    * @param   file   the file to open.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static PrintWriter openWriteText(File file) throws IOException {
      Charset charset = Charset.defaultCharset();
      PrintWriter pw = openWriteText(file, charset);
      return pw;
      }

   /**
    * Opens a text file for writing.
    * @param   filename   the file to open.
    * @param   charset   the encoding of the text.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static PrintWriter openWriteText(String filename, Charset charset) throws IOException {
      File file = new File(filename);
      PrintWriter pw = openWriteText(file, charset);
      return pw;
      }

   /**
    * Opens a text file for writing.
    * @param   file   the file to open.
    * @param   charset   the encoding of the text.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static PrintWriter openWriteText(File file, Charset charset) throws IOException {
      FileOutputStream fos = new FileOutputStream(file);
      PrintWriter pw = asPrintWriter(fos, charset);
      return pw;
      }


//---------------------------
// openAppend()
//---------------------------

   /**
    * Opens a file to append to.
    * @param   filename   the file to open.
    * @return   a stream to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static OutputStream openAppend(String filename) throws IOException {
      File file = new File(filename);
      OutputStream os = openAppend(file);
      return os;
      }

   /**
    * Opens a file to append to.
    * @param   file   the file to open.
    * @return   a stream to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static OutputStream openAppend(File file) throws IOException {
      boolean append = true;
      OutputStream os = new FileOutputStream(file, append);
      return os;
      }

   /**
    * Opens a text file to append to.
    * @param   filename   the file to open.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static Writer openAppendText(String filename) throws IOException {
      Charset charset = Charset.defaultCharset();
      Writer writer = openAppendText(filename, charset);
      return writer;
      }

   /**
    * Opens a text file to append to.
    * @param   file   the file to open.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static PrintWriter openAppendText(File file) throws IOException {
      Charset charset = Charset.defaultCharset();
      PrintWriter pw = openAppendText(file, charset);
      return pw;
      }

   /**
    * Opens a text file to append to.
    * @param   filename   the file to open.
    * @param   charset   the encoding of the text.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static PrintWriter openAppendText(String filename, Charset charset) throws IOException {
      File file = new File(filename);
      PrintWriter pw = openAppendText(file, charset);
      return pw;
      }

   /**
    * Opens a text file to append to.
    * @param   file   the file to open.
    * @param   charset   the encoding of the text.
    * @return   a writer to write in.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static PrintWriter openAppendText(File file, Charset charset) throws IOException {
      boolean append = true;
      FileOutputStream fos = new FileOutputStream(file, append);
      PrintWriter pw = asPrintWriter(fos, charset);
      return pw;
      }


//---------------------------
// copy()
//---------------------------

   /**
    * Copies one file. The copy last modified time is set to the original's. The
    * copy is done using {@link FileChannel}s.
    * @param   in   the original file.
    * @param   out   the copy.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void copy(File in, File out) throws IOException {
      try (FileInputStream fis = new FileInputStream(in);
           FileOutputStream  fos = new FileOutputStream(out);
           ) {
         copy(fis, fos);

         long last_modified = in.lastModified();
         out.setLastModified(last_modified);
         }
      }

   /**
    * Copies one file stream into the other. The copy is done using
    * {@link FileChannel}s.
    * @param   fis   the source stream.
    * @param   fos   the sink stream.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void copy(FileInputStream fis, FileOutputStream fos) throws IOException {
      try (FileChannel in = fis.getChannel();
           FileChannel  out =fos.getChannel();
            ) {
         copy(in, out);
         }
      }

   /**
    * Copies one channel into the other.
    * @param   in   the source channel.
    * @param   out   the sink channel.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void copy(FileChannel in, FileChannel out) throws IOException {
      long position = 0L;
      long count = in.size();
      in.transferTo(position, count, out);
      }

// TODO: FileUtil.move(File, File)
// TODO: FileUtil.removeExtension() http://stackoverflow.com/questions/941272/how-do-i-trim-a-file-extension-from-a-string-in-java

// http://msdn.microsoft.com/en-us/library/system.io.file_methods.aspx

// Create  Overloaded. Creates a file in the specified path.
// CreateText      Creates or opens a file for writing UTF-8 encoded text.

//     Decrypt Decrypts a file that was encrypted by the current account using the Encrypt method.
//     Encrypt Encrypts a file so that only the account used to encrypt the file can decrypt it.

//     GetAccessControl        Overloaded. Gets a FileSecurity object that encapsulates the access control list (ACL) entries for a specified file.
//     GetAttributes   Gets the FileAttributes of the file on the path.
//     GetCreationTime Returns the creation date and time of the specified file or directory.
//     GetCreationTimeUtc      Returns the creation date and time, in coordinated universal time (UTC), of the specified file or directory.
//     GetLastAccessTime       Returns the date and time the specified file or directory was last accessed.
//     GetLastAccessTimeUtc    Returns the date and time, in coordinated universal time (UTC), that the specified file or directory was last accessed.
//     GetLastWriteTime        Returns the date and time the specified file or directory was last written to.
//     GetLastWriteTimeUtc     Returns the date and time, in coordinated universal time (UTC), that the specified file or directory was last written to.
//     SetAccessControl        Applies access control list (ACL) entries described by a FileSecurity object to the specified file.
//     SetAttributes   Sets the specified FileAttributes of the file on the specified path.
//     SetCreationTime Sets the date and time the file was created.
//     SetCreationTimeUtc      Sets the date and time, in coordinated universal time (UTC), that the file was created.
//     SetLastAccessTime       Sets the date and time the specified file was last accessed.
//     SetLastAccessTimeUtc    Sets the date and time, in coordinated universal time (UTC), that the specified file was last accessed.
//     SetLastWriteTime        Sets the date and time that the specified file was last written to.
//     SetLastWriteTimeUtc     Sets the date and time, in coordinated universal time (UTC), that the specified file was last written to.

   }
